/*
 *	Copyright (c) 2009, libBasement
 *	All rights reserved.
 *	
 *	Redistribution and use in source and binary forms, with or without modification,
 *	are permitted provided that the following conditions are met:
 *	
 *	Redistributions of source code must retain the above copyright notice,
 *	this list of conditions and the following disclaimer.
 *	Redistributions in binary form must reproduce the above copyright notice,
 *	this list of conditions and the following disclaimer in the documentation
 *	and/or other materials provided with the distribution.
 *	Neither the name of the <ORGANIZATION> nor the names of its contributors may
 *	be used to endorse or promote products derived from this software without
 *	specific prior written permission.
 *	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 *	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
 *	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 *	(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 *	ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 *	SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "BArray.h"
#include "BString.h"
#include "BException.h"
#include <algorithm>

namespace Basement
{
	Array *$(Object *object, ...)
	{
		va_list arg;
		va_start(arg, object);
		
		Array *result = Array::alloc()->initWithObjects(object, arg);
		
		va_end(arg);
		
		return result->autorelease<Array *>();
	}
	
	void Array::dealloc()
	{
		this->foreach(lambda(void, (Array *sender, UInteger index),
							 sender->removeObjectAtIndex(index)));
		delete _objects;
		
		Object::dealloc();
	}
	
#pragma mark Initializers
	
	Array *Array::init()
	{
		if(Object::init())
		{
			_objects = new ObjectVector;
			
			return this;
		}
		return NULL;
	}
	
	Array *Array::withObjects(Object *object, ...)
	{
		va_list arg;
		va_start(arg, object);
		
		Array *result = Array::alloc()->initWithObjects(object, arg);
		
		va_end(arg);
		
		return result->autorelease<Array *>();
	}
	
	Array *Array::initWithObjects(Object *object, ...)
	{
		va_list arg;
		va_start(arg, object);
		
		Array *result = this->initWithObjects(object, arg);
		
		va_end(arg);
		
		return result;
	}
	
	Array *Array::initWithObjects(Object *object, va_list arg)
	{
		if(this->init())
		{
			this->addObject(object);
			
			Object *ptr = va_arg(arg, Object *);
			while (ptr != NULL)
			{
				this->addObject(ptr);
				ptr = va_arg(arg, Object *);
			}
			
			return this;
		}
		
		return NULL;
	}
	
	Array *Array::initWithArray(Array *other)
	{
		if(this->init())
		{
			forall(Object *, object, other)
			{
				this->addObject(object);
			}
			
			return this;
		}
		return NULL;
	}
	
#pragma mark -
#pragma mark Interaction
	
	String *Array::description()
	{
		String *description = $("(\n");
		
		Array::ObjectEnumerator objectEnumerator = this->objectEnumerator();
		Object *object;
		while (object = objectEnumerator.nextObject())
		{
			description->appendString($("\t"));
			description->appendString(object->description());
			description->appendString($("\n"));
		}
		
		description->appendString($(")"));
		
		return description;
	}
	
	void Array::foreach(void(*callback)(Array *, Object *))
	{
		BParamaterAssert(callback);
		
		for (int i = 0; i < this->count(); i++)
		{
			callback(this, this->objectAtIndex(i));
		}
	}
	
	void Array::foreach(void(*callback)(Array *, Object *, UInteger))
	{
		BParamaterAssert(callback);
		
		for (int i = 0; i < this->count(); i++)
		{
			callback(this, this->objectAtIndex(i), i);
		}
	}
	
	void Array::foreach(void(*callback)(Array *, UInteger))
	{
		BParamaterAssert(callback);
		
		for (int i = 0; i < this->count(); i++)
		{
			callback(this, i);
		}
	}
	
	UInteger Array::indexOfObject(Object *object)
	{
		for (int i = 0; i < this->count(); i++)
		{
			if(object->isEqualTo(this->objectAtIndex(i)))
			{
				return i;
			}
		}
		
		return Range::NotFound;
	}
	
	UInteger Array::indexOfObjectIdenticalTo(Object *object)
	{
		for (int i = 0; i < this->count(); i++)
		{
			if(object == this->objectAtIndex(i))
			{
				return i;
			}
		}
		
		return Range::NotFound;
	}
	
	bool Array::isEqualToArray(Array *other) const
	{
		if(count() == other->count())
		{
			for (Integer index = 0; index < count(); index++)
			{
				if(!objectAtIndex(index)->isEqualTo(other->objectAtIndex(index)))
				{
					return false;
				}
			}
		}
		
		return true;
	}
	
#pragma mark -
#pragma mark Deriving New Arrays
	
	Array *Array::arrayByAddingObject(Object *object)
	{
		Array *result = Array::alloc()->initWithArray(this);
		result->addObject(object);
		return result;
	}
	
	Array *Array::arrayByAddingObjects(Array *objects)
	{
		Array *result = Array::alloc()->initWithArray(this);
		forall(Object *, object, objects)
		{
			result->addObject(object);
		}
		return result;
	}
	
	Array *Array::filteredArrayWith(bool(*callback)(Array *, Object *, void *userinfo), void *userinfo)
	{
		BParamaterAssert(callback);
		Array *result = Array::alloc()->init();
		forall(Object *, object, this)
		{
			if(callback(this, object, userinfo))
			{
				result->addObject(object);
			}
		}
		return result;
	}
	
	Array *Array::subarrayWithRange(Range range)
	{
		Array *array = Array::alloc()->init();
		for (int i = range.length; i < range.length + range.location; i++)
		{
			array->addObject(this->objectAtIndex(i));
		}
		return array->autorelease<Array *>();
	}
	
	struct _ArraySortCallbackObject {
		//Members
		void *context;
		SortFunction callback;
		
		//Functions
		_ArraySortCallbackObject() : callback(NULL), context(NULL) {}
		_ArraySortCallbackObject(SortFunction sortCallback, void *inContext) : callback(sortCallback), context(inContext) {}
		bool operator() (Object *a, Object *b) { return callback(a, b, context); }
	};
	
	Array *Array::sortedArrayUsingFunction(SortFunction callback, void *context)
	{
		BParamaterAssert(callback);
		
		_ArraySortCallbackObject callbackObject(callback, context);
		
		ObjectVector objects = ObjectVector(*_objects);
		std::sort< ObjectVector::iterator, _ArraySortCallbackObject >(objects.begin(), objects.end(), callbackObject);
		
		Array *result = Array::alloc()->init();
		for (ObjectVector::iterator iter = objects.begin(); iter != objects.end(); iter++)
		{
			result->addObject(*iter);
		}
		
		return result->autorelease<Array *>();
	}
	
	void Array::sortUsingFunction(SortFunction callback, void *context)
	{
		BParamaterAssert(callback);
		
		_ArraySortCallbackObject callbackObject(callback, context);
		
		std::sort< ObjectVector::iterator, _ArraySortCallbackObject >(_objects->begin(), _objects->end(), callbackObject);
	}
	
#pragma mark -
#pragma mark Manipulation
	
	void Array::addObject(Object *object)
	{
		BParamaterAssert(object);
		
		_objects->push_back(object);
		object->retain();
	}
	
	void Array::addObjectsFromArray(Array *array)
	{
		BParamaterAssert(array);
		
		forall(Object *, object, array)
		{
			this->addObject(object);
		}
	}
	
	void Array::insertObject(Object *object, UInteger index)
	{
		BParamaterAssert(object);
		
		_objects->insert(_objects->begin() + index, object);
		object->retain();
	}
	
	void Array::removeObjectAtIndex(UInteger index)
	{
		ObjectVector::iterator iter = _objects->begin();
		Object *object = _objects->at(index);
		_objects->erase(iter + index);
		object->release();
	}
	
	void Array::removeObjectsInRange(Range &range)
	{
		for (int i = range.length; i < range.length + range.location; i++)
		{
			this->removeObjectAtIndex(i);
		}
	}
	
	void Array::removeObjects(Array *objects)
	{
		forall(Object *, object, this)
		{
			this->removeObject(object);
		}
	}
	
	void Array::removeObject(Object *object)
	{
		UInteger index = this->indexOfObject(object);
		if(index != Range::NotFound)
		{
			this->removeObjectAtIndex(index);
		}
	}
}
